#include <QtGui>
#include "field.hh"

#define TETRA_MOVE_BASE_TIMEOUT 1000 // Время между движениями тетры вниз
#define SCORE_FACTOR 4 // Множитель очков после повышения уровня
#define LINE_SCORE_FACTOR 2 // Множитель очков за линии после повышения уровня
#define LEVELUP_BASE_SCORE 500 // Количество очков для первого повышения уровня
// Начальные очки за 1, 2, 3, 4 линии
#define ONE_LINE_BASE_SCORE 10
#define TWO_LINE_BASE_SCORE 30
#define THREE_LINE_BASE_SCORE 70
#define FOUR_LINE_BASE_SCORE 150

QColor tetraColors[] = {Qt::black, Qt::red, Qt::blue, Qt::green,
                       Qt::yellow, Qt::cyan, Qt::gray, Qt::magenta};
/**
* =============================================================================
* Конструктор класса Field
* =============================================================================
* @param columnNum
*  //TODO Для чего это?
* @param rowNum
*  //TODO Для чего это?
* @param columnsCount
*  Количество столбцов стакана
* @param rowsCount
*  Количество строк стакана
* @param cubeSize
*  Размер кубика в стакане
* @param parent
*  Предок класса
* =============================================================================
*/
Field::Field(int columnNum, int rowNum, uint columnsCount, uint rowsCount,
             float cubeSize, QWidget *parent) : QWidget(parent,0) {

    this->ColumnNum()=columnNum;
    this->RowNum()=rowNum;
    this->columnsCount = columnsCount;
    this->rowsCount = rowsCount;
    this->cubeSize = cubeSize;

    //TODO Возможен segfault из-за неверной работы с указателями.
    field = new char * [rowsCount];
    for (uint i = 0; i < this->rowsCount; i++) {
        field[i] = new char[columnsCount];
        for (uint j = 0; j < this->columnsCount; j++) {
            field[i][j] = 0;
        }
    }

    int randValue = getRandomValue();
    tetra = new Tetra(4, randValue, randValue);

    level = 0;
    levelup_score = LEVELUP_BASE_SCORE;
    lines = 0;
    one_line_score = ONE_LINE_BASE_SCORE;
    two_line_score = TWO_LINE_BASE_SCORE;
    three_line_score = THREE_LINE_BASE_SCORE;
    four_line_score = FOUR_LINE_BASE_SCORE;
    score=0;
    tetra_move_timeout = TETRA_MOVE_BASE_TIMEOUT;

    tetraMoveTimer = new QTimer(this);
    tetraMoveTimer->start(tetra_move_timeout);
    QObject::connect(tetraMoveTimer, SIGNAL(timeout()), this, SLOT(moveTetraDown()));
}

Field::~Field() {
    // Возвращаем память массива field в кучу
    for (uint i = 0; i < columnsCount; i++) {
        delete [] field[i];
    }
    delete field;

    delete tetra;
}

/**
* =============================================================================
* Движение тетры вниз на одну позицию
* =============================================================================
*/
void Field::moveTetraDown() {
    deleteTetra();
    RowNum()++;
    //tetra->rotate(false);
    if (!insertTetra()) {
        RowNum()--;
        insertTetra();
        deleteFillingLine();

        ColumnNum() = 0;
        RowNum() = 0;
        int randValue = getRandomValue();
        tetra->selectTetra(randValue, randValue);
        insertTetra();
     }
    update();
}

/**
* =============================================================================
* Обработка перехода на новый уровень
* =============================================================================
*/
void Field::nextLevel() {
    levelup_score *= SCORE_FACTOR;
    one_line_score *= LINE_SCORE_FACTOR;
    two_line_score *= LINE_SCORE_FACTOR;
    three_line_score *= LINE_SCORE_FACTOR;
    four_line_score *= LINE_SCORE_FACTOR;
    tetra_move_timeout -= level * SCORE_FACTOR;
    level++;
    tetraMoveTimer->stop();
    tetraMoveTimer->start(tetra_move_timeout);
    qDebug() << "level: "<<level;
}

/**
* =============================================================================
* Рисование на виджете
* =============================================================================
*/
void Field::paintEvent(QPaintEvent * event){
    Q_UNUSED(event); // Обозначение неиспользуемой переменной
    drawField();

    QFont font;
    font.setPixelSize(21);
    QString text = QObject::tr("Счёт: %1").arg(QString::number(score));
    drawString(40, 40, font, text);
    text = QObject::tr("Линии: %1").arg(QString::number(lines));
    drawString(40, 60, font, text);
    text = QObject::tr("Уровень: %1").arg(QString::number(level));
    drawString(40, 80, font, text);
}

/**
* =============================================================================
* Обработка нажатий на клавиши
* =============================================================================
* @param event
*  Событие нажатия на кнопку
*  Из него извлекаются нажатые клавиши
*/
void Field::keyPressEvent(QKeyEvent * event) {
    switch (event->key()) {
    case Qt::Key_Q:     // поворот по часовой стрелке
        deleteTetra();
        tetra->rotate(true);
        if (!insertTetra()){
            tetra->rotate(false);
            insertTetra();
        };
        break;
    case Qt::Key_W:     // поворот против часовой стрелки
        deleteTetra();
        tetra->rotate(false);
        if (!insertTetra()){
            tetra->rotate(true);
            insertTetra();
        };
        break;
    case Qt::Key_Up: // Смещение вверх
        /*deleteTetra();
        RowNum()--;
        if (!insertTetra()){
            RowNum()++;
            insertTetra();
        };
        */
        deleteTetra();
        tetra->rotate(true);
        if (!insertTetra()){
            tetra->rotate(false);
            insertTetra();
        };
        break;
    case Qt::Key_Down: // Смещение вниз
        /*deleteTetra();
        RowNum()++;
        //tetra->rotate(false);
        if (!insertTetra()){
            RowNum()--;
            insertTetra();
        };*/
        moveTetraDown();
        break;
    case Qt::Key_Left: // Перемещение тетры влево
        //TODO Регулировать выход за левую границу стакана
        deleteTetra();
        ColumnNum() --;
        if (!insertTetra()){
            ColumnNum()++;
            insertTetra();
        };
        break;
    case Qt::Key_Right: // Перемещение тетры вправо
        //TODO Регулировать выход за правую границу стакана
        deleteTetra();
        ColumnNum()++;
        if (!insertTetra()){
            ColumnNum()--;
            insertTetra();
        };
        break;
    case Qt::Key_Space: // Быстрое, решительное движение фигуры вниз
        // TODO Сделать движение фигуры вниз
        deleteTetra();
        insertTetra();
        break;
    case Qt::Key_1:
        deleteTetra();
        tetra->selectTetra(1, 1);
        insertTetra();
        break;
    case Qt::Key_2:
        tetra->selectTetra(2, 2);
        break;
    case Qt::Key_3:
        tetra->selectTetra(3, 3);
        break;
    case Qt::Key_4:
        tetra->selectTetra(4, 4);
        break;
    case Qt::Key_5:
        tetra->selectTetra(5, 5);
        break;
    case Qt::Key_6:
        tetra->selectTetra(6, 6);
        break;
    case Qt::Key_7:
        tetra->selectTetra(7, 7);
        break;
    case Qt::Key_Escape: //Выходим по горячей клавише
        gamePause();
        //qApp->quit();
    default:
        break;
    }
}

/**
* =============================================================================
* Рисования квадрата
* =============================================================================
* @param pos
*  Начальная позиция
* @param oColor
*  Внешний цвет (контур и блик)
* @param iColor
*  Внутренний цвет
* =============================================================================
*/
void Field::drawQuad(QPointF pos, QColor oColor, QColor iColor) {
    QPainter painter;
    QPen pen;
    QBrush brush;

    painter.begin(this);
    //painter.eraseRect(QRect(pos.x, pos.y, size+pos.x, size+pos.y));
    brush.setColor(QColor(0, 127, 0));
    pen.setWidth(2);
    painter.setPen(pen);
    QSizeF sizeF(cubeSize,cubeSize);
    QRectF rect(pos,sizeF);
    pen.setColor(iColor);
    painter.setBrush(oColor);
    painter.drawRect(rect);

    painter.end();
}

/**
* =============================================================================
* Рисования стакана
* =============================================================================
*/
void Field::drawField() {
    // Точка, где находится верхний левый край стакана
    QPointF pos((cubeSize * columnsCount)/2, 10);
    // TODO Рисование стакана

    // Рисование фигур в стакане
    for (uint i = 0; i < rowsCount; i++) {
        for (uint j = 0; j < columnsCount; j++) {
            //if(field[i][j] != 0) {
                drawQuad(QPointF(pos.x() + (cubeSize * j), pos.y() + (cubeSize * i)),
                         tetraColors[ uint(field[i][j]) ], Qt::white);
            //}
        }
    }
}

/**
* =============================================================================
* Рисования строки текста
* =============================================================================
* @param offsetWidth
*  Смещение счёта относительно правого верхнего края по ширине
* @param offsetHeight
*  Смещение счёта относительно правого верхнего края по высоте
* @param font
*  Шрифт
* @param text
*  Выводимая строка текста
* =============================================================================
*/
void Field::drawString(uint offsetWidth, uint offsetHeight, QFont font, QString text) {
    QPainter painter;
    painter.begin(this);

    painter.setFont(font);

    QFontMetrics fm = painter.fontMetrics();
    qreal fontWidth = fm.width(text);
    qreal fontHeight = fm.height();
    qreal fontLeft = width() - (fontWidth + offsetWidth);
    qreal fontTop = offsetHeight;
    QRect rect = QRect(fontLeft, fontTop, fontWidth, fontHeight);
    painter.drawText(rect, Qt::AlignRight, text);

    painter.end();

    update(rect);
}

/**
* =============================================================================
* Возвращает случайное число
* =============================================================================
*/
uint Field::getRandomValue() { //TODO сделать реализацию хорошего рандома
    qsrand(QTime(0,0,0).msecsTo(QTime::currentTime()));
    uint randValue = qrand() % 6 + 1;
    qDebug() << randValue;
    return randValue;
}

/**
* =============================================================================
* Остановка игры
* =============================================================================
*/
void Field::gamePause() {
    //TODO добавить затенение экрана
    //TODO Исправить баг с движением фигур при нажатии на клавиши во время паузы
    if (tetraMoveTimer->isActive()) {
        tetraMoveTimer->stop();
    } else {
        tetraMoveTimer->start(tetra_move_timeout);
    }
}

/**
* =============================================================================
* Добавление фигуры на поле
* =============================================================================
* @return false
*  Нелья добавить
* @return true
*  Можно добавить
* =============================================================================
*/
bool Field::insertTetra() {
    bool canInsert=testTetra();
    if (canInsert) {
        for (uint i = 0; i < tetra->TypeSize(); i++) {
            for (uint j = 0; j < tetra->TypeSize(); j++) {
                if (tetra->TypeBit(i, j)) {
                    field[RowNum() + i][ColumnNum() + j]=tetra->TypeBit(i,j);
                }
            }
        }
    }
    this->update();
    return canInsert;
}

/**
* =============================================================================
* Проверяет, можно ли добавить фигуру на поле
* =============================================================================
* @return false
*  Нелья
* @return true
*  Можно
* =============================================================================
*/
bool Field::testTetra() {
    int x=ColumnNum();  // Сугубо для лучшей читаемости
    int y=RowNum();     // Сугубо для лучшей читаемости
    bool canInsert = true; // Возможность поставить фигуру в указанную позицию
    // проверка на возможность вставки
    for (uint i = 0; i < tetra->TypeSize(); i++) {
        for (uint j = 0; j < tetra->TypeSize(); j++) {
            if ((tetra->TypeBit(i, j) != 0) && canInsert) {
                if((((x + j) < 0) || ((x + j) >= columnsCount)) ||
                   ((((y + i) < 0) || ((y + i) >= rowsCount)) ||
                    (field[y + i][x + j] !=0 ))) {
                    canInsert = false;
                }
            }
        }
    }
    return canInsert;
}

/**
* =============================================================================
* Удаление тетры
* =============================================================================
*/
void Field::deleteTetra() { // Удаляем тетру из стакана
    for (uint i = 0; i < tetra->TypeSize(); i++) {
        for (uint j = 0; j < tetra->TypeSize(); j++) {
            if (tetra->TypeBit(i, j)) {
                field[RowNum() + i][ColumnNum() + j] = 0;
            }
        }
    }
}

/**
* =============================================================================
* Проверяет, заполнена ли строка в стакане
* =============================================================================
* @param n
*  Номер строки в стакане
* @return false
*  Не заполнена
* @return true
*  Заполнена
* =============================================================================
*/
bool Field::isFill(uint n)
{
    bool rez=true;
    uint i=0;
    if (n<rowsCount){
        while ((i<columnsCount) && rez){
            if (field[n][i]==0){
                rez=false;
            }
            i++;

        }
    }
    return rez;
}

/**
* =============================================================================
* Удаление заполненных линий из стакана
* =============================================================================
* @return
*  Количество удалённых строк
* =============================================================================
*/
size_t Field::deleteFillingLine() {
    uint k=rowsCount-1, num=0;
    while (k>0){
        if (isFill(k)){
            num++;
            for (uint i=k;i>0;i--){
                for (uint j=0;j<columnsCount;j++){
                   field[i][j]=field[i-1][j];
                }
            }
            //score+=num*100+ (num-1)*100;

        }
        else{
            k--;
        }
    }

    lines += num;

    switch (num){
    case 1:
        score += one_line_score;
        break;
    case 2:
        score += two_line_score;
        break;
    case 3:
        score += three_line_score;
        break;
    case 4:
        score += four_line_score;
        break;
    }

    if (score >= levelup_score) {
        nextLevel();
    }

    return num;
}
